
#include <mpi.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <fstream>
#include "mnt.h"
#include "algo.h"

using namespace std;
const float epsylon = 0.01 ;
const int ETIQUETTE=1000;
MPI_Status statut;
int changement = false;


void algorithmeCuvette(MNT mnt, int pid, const int nprocs) {



    //On récupere le nombre de lignes et de colonnes de la matrice mnt
    int nbLignes = mnt.ligne();
    int nbColonnes = mnt.colonne();

    int reste = nbLignes%nprocs;

    int nbLignesParProcs = nbLignes/nprocs;
    int nbLignesParProcsTmp;

    /* Le proc 0 réparti les lignes de la matrice sur tous les procs */
    if (pid == 0) {


        int debutEnvoi = 0;

        /*Pour chaque procs on lui communique le nombre de lignes qu'il doit recevoir
         Et on lui envoi ces lignes*/
        for (int i=0; i<nprocs; i++) {

            if (reste > 0) {

                nbLignesParProcsTmp = nbLignesParProcs + 1;
                reste = reste - 1;

            }else {

                nbLignesParProcsTmp = nbLignesParProcs;
            }


            // On envoi le nombre de ligne qu'il doit recevoir
            MPI_Send(&nbLignesParProcsTmp,1,MPI_INT,i,123,MPI_COMM_WORLD);
            // On envoi les lignes

            for (int j = 0 ; j < nbLignesParProcsTmp; j++){

                MPI_Send(&mnt.monTableau()[debutEnvoi],nbColonnes,MPI_FLOAT,i,ETIQUETTE,MPI_COMM_WORLD);
                 debutEnvoi += nbColonnes;

            }

        }

    }


    /* Reception du nombre de lignes que chaque procs doit recevoir */
    MPI_Recv(&nbLignesParProcs,1,MPI_INT,0,123,MPI_COMM_WORLD,&statut);

    /* Création du mnt pour chaque processeur */
    MNT mntLigne(nbLignesParProcs,nbColonnes);

    int recu = 0 ;
    for (int c = 0 ; c < nbLignesParProcs ; c++){
    /* Reception des lignes sur tous les procs */
    MPI_Recv(&mntLigne.monTableau()[recu],nbLignesParProcs*nbColonnes,MPI_FLOAT,0,ETIQUETTE,MPI_COMM_WORLD,&statut);

    recu+=nbColonnes;

    }

    // On calcul le max pour chaque matrice
    float max = mntLigne.calculMax();

    float maxGlob[nprocs];

    // On récupère le max de tous les processeurs
    MPI_Gather(&max,1,MPI_FLOAT,&maxGlob,1,MPI_FLOAT,0,MPI_COMM_WORLD);

    float maxGlobal ;

    if(pid == 0){

        // On recherche le max global parmis tous les max
        maxGlobal = calculMax(maxGlob,1,nprocs) + epsylon;
    }


    // On communique à tous le monde le max
    MPI_Bcast(&maxGlobal,1,MPI_FLOAT,0,MPI_COMM_WORLD);

    /* On remplit notre matrice avec le max partout sauf sur les bords qui
     sont les mêmes que ceux du mnt de départ */
    float * W = mntLigne.remplir(nbLignes,pid,nprocs,maxGlobal);


    // ############### Etape 1 : etape remplissage ######################
    if(pid == 0){
     cout << "Etape 1 : Remplissage ..." << endl;
    }

     // Tableau qui contient nos lignes, la ligne du dessus et la ligne du dessous
    MNT mntW(nbLignesParProcs+2,nbColonnes,mnt.data(),mnt.x(),mnt.y(),mnt.cell());

    // On initialise notre tableau pour recevoir
    mntW.initialiserTableau(W);

    int conditions[nprocs];
    int conditionGlob ;
    int  condition ;
    conditionGlob = true ;

    // ############### Etape 1 : deroulement algorithme ######################


         // Tant que toute les conditions ne sont pas à faux
        while(conditionGlob){

        // On envoie nos lignes au dessus et en dessous
        mntW.envoi(nprocs,pid);


        // On récupère la condition de l'algorithme
        condition = mntW.algorithmeRemplisage(mntLigne);


        // On récupère toutes les conditions
        MPI_Gather(&condition,1,MPI_INT,&conditions,1,MPI_FLOAT,0,MPI_COMM_WORLD);

        if(pid == 0){

            // On regarde si toutes les conditions sont à fausse
            conditionGlob = calculerCondition(conditions,nprocs);
        }

        // On communique à tous le monde si on doit continuer l'algorithme
         MPI_Bcast(&conditionGlob,1,MPI_INT,0,MPI_COMM_WORLD);
         }


        // On envoie nos lignes au dessus et en dessous
        mntW.envoi(nprocs,pid);

        // On récupère les lignes de chaque processeurs et on écrit dans le fichier
        mntW.retourEcriture(nbColonnes,pid,nprocs,nbLignesParProcs,nbLignes,nbColonnes,"mntRemplissage.txt");



    // ############### Etape 2 DIRECTIONS D ECOULEMENT ######################

    if(pid == 0){
        cout << "Etape 2 : Direction d'écoulement ..." << endl;
    }

    MNT mntDirection(nbLignesParProcs,nbColonnes,mnt.data(),mnt.x(),mnt.y(),mnt.cell());
    mntDirection.algoDirection(mntW);


    // On récupère les lignes de chaque processeurs et on écrit dans le fichier
    mntDirection.retourEcriture(0,pid,nprocs,nbLignesParProcs,nbLignes,nbColonnes,"mntDirectionEcoulement.txt");







    // ############### Etape 3 FLUX D ACCUMULATION ######################
	
	if(pid == 0){
        cout << "Etape 3 : Flux d'accumulation ..." << endl;
    }
	
	// MNT qui contient le MNT des direction d'écoulement appartenant à chaque procs + la derniere ligne du proc précédent et la 1ere ligne du procs suivant
    MNT mntDirectionEntier(nbLignesParProcs+2,nbColonnes,mnt.data(),mnt.x(),mnt.y(),mnt.cell());
	
    // On initialise notre tableau pour recevoir
    mntDirectionEntier.initialiserTableau(mntDirection.monTableau());


	// On envoie nos lignes au dessus et en dessous
	mntDirectionEntier.envoi(nprocs,pid);


	//On créer le MNT qui va contenir le flux 
	//On lui donne une taille du nombre de ligne que contient le processeur plus 2 lignes pour représenter la 1ere ligne
	//du proc en dessous et la derniere ligne du proc au dessus. Ceci afin de gérer les chemins de flux qui parcours plusieurs procs.
	MNT mntFlux(nbLignesParProcs+2,nbColonnes,mnt.data(),mnt.x(),mnt.y(),mnt.cell());
	mntFlux.initialiserFlux();
	
	//On créer un tableau qui nous indique si on a déja visité un point au cours de l'algo de calcul de flux d'accumulation
	MNT mntDejaVu(nbLignesParProcs+2,nbColonnes,mnt.data(),mnt.x(),mnt.y(),mnt.cell());
	//On initialise le tableau DejaVu à faux pour tout le monde
	for (int i=0; i < (nbLignesParProcs + 2) * (nbColonnes); i++) {
		mntDejaVu.monTableau()[i] = false;
	}
	
	//On créer un boolean qui va nous indiquer si on a effectué un changement :
	//Cela veut dire qu'on s'est déverssé sur un point qui n'était pas sur le même processeur (Dans le cas d'un chemin partagé)
	//int changement = false;
	
	//On fait en premier le traitement des chemins locaux
	


	//pour tout les point de ma matrice locale
    for (int i = nbColonnes; i< ((nbLignesParProcs*nbColonnes) + nbColonnes); i++) {
		
		//Si personne ne pointe sur moi, je suis un point de départ
		if ( mntDirectionEntier.sommeDeversemmentVoisin(i) == 0) {
            mntFlux.algoFlux(mntDirectionEntier,mntDejaVu,i);
		}
	}


    //mntDirectionEntier.afficherMNT();
    //On créer un tableau qui va recvoir une ligne du procs avant lui et une ligne du proc apres lui pour le flux
	MNT mntLignesPartages(2,nbColonnes,mnt.data(),mnt.x(),mnt.y(),mnt.cell());
	
	// On initialise notre tableau
	mntLignesPartages.initialiserFlux();
	
	//On envoi les lignes au procs au dessus et en dessous
	mntFlux.envoiLignePartageFlux(mntLignesPartages,nprocs,pid);
	
	//On met à jour les points déja vu pour les lignes partagées entre plusieurs procs
	mntDejaVu.envoi(nprocs,pid);
	
	
    //mntLignesPartages.afficherMNT();
	//On gère ensuite les chemins partagés par plusieurs procs :
	
	//on test si il y a eu un changement sur les lignes partagées (cela voudrai dire qu'il existe des chemins qui traversent plusieurs processeurs)
	
	int changements[nprocs];
	int changementGlob = false;
	
	//On récupère tous les changements
	MPI_Gather(&changement,1,MPI_INT,&changements,1,MPI_INT,0,MPI_COMM_WORLD);
	
	
	if(pid == 0){
		
		for (int i=0; i<nprocs ; i++) {
			if (changements[i]) {
				changementGlob = true;
			}
		}
	}



	//On communique à tous le monde si on doit continuer l'algorithme
	MPI_Bcast(&changementGlob,1,MPI_INT,0,MPI_COMM_WORLD);
	
	
	while (changementGlob) {



		//On réinitialise le flux de la 1ere et de la derniere ligne (cad celles qui appartiennent au proc d'avant et à celui d'apres)
		mntFlux.reinitialiserFluxLignesPartagees();
		
		//On remet notre changement à faux
		changement = false;
		changementGlob = false;
		

		//Pour la premiere ligne paratge (celle qui vient du proc d'avant)
		for (int i=0; i<nbColonnes; i++) {
			if ( (mntLignesPartages.monTableau()[i] > 1) ) {
				mntFlux.monTableau()[i+nbColonnes] += mntLignesPartages.monTableau()[i] - 1;
                mntFlux.algoFlux(mntDirectionEntier,mntDejaVu,i+nbColonnes);
           // cout << "changement 1 " << changement << endl ;
            }
			
		}
		
		//Pour la deuxieme ligne paratge (celle qui vient du proc d'apres)
		for (int i=nbColonnes; i<nbColonnes*2; i++) {
			if ( (mntLignesPartages.monTableau()[i] > 1) ) {
				mntFlux.monTableau()[((nbLignesParProcs-1) * nbColonnes) + i] += mntLignesPartages.monTableau()[i] - 1;
                mntFlux.algoFlux(mntDirectionEntier,mntDejaVu,((nbLignesParProcs-1) * nbColonnes) + i);
               // cout << "changement 2 " << changement << endl ;
            }
		}
		
		//On met à jour les points déja vu pour les lignes partagées entre plusieurs procs
		mntDejaVu.envoi(nprocs,pid);
		
		//On envoi les lignes partagées au procs au dessus et en dessous
		mntFlux.envoiLignePartageFlux(mntLignesPartages,nprocs,pid);
		
		//On récupère touts les changements
		MPI_Gather(&changement,1,MPI_INT,&changements,1,MPI_INT,0,MPI_COMM_WORLD);
		
		if(pid == 0){
			
			for (int i=0; i<nprocs ; i++) {
				if (changements[i]) {
					changementGlob = true;
				}
			}
			
		}
		

		//On communique à tous le monde si on doit continuer l'algorithme
		MPI_Bcast(&changementGlob,1,MPI_INT,0,MPI_COMM_WORLD);
		
		
	}
	
	
    // On récupère les lignes de chaque processeurs et on écrit dans le fichier
    mntFlux.retourEcriture(nbColonnes,pid,nprocs,nbLignesParProcs,nbLignes,nbColonnes,"mntFlux.txt");
	
    /*
	 MPI_Barrier(MPI_COMM_WORLD);
	 cout << "proc "<< pid << " Affiche le flux" << endl ;
	 mntFlux.afficherMNT();
	 */
	
}

/**
* Parcours tous le tableau de booléens
* Détermine si toutes les conditions sont à fausse
*/
bool calculerCondition(int * conditions , int size){

    int i = 0 ;
    bool condition = false ;

    while(i < size && !condition){


        condition = conditions[i];

        i++ ;
    }

    return condition ;
}

/**
* Permet de trouver un max dans un tableau
*/
float calculMax(float * ligne , int size , int nbColonnes){

    int max = ligne[0];

    for(int i = 1 ; i < size * nbColonnes ; i ++){

        if(ligne[i] > max){

            max = ligne[i];
        }
    }

    return max ;
}
